// ThreadsAndLocks.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <memory>
#include <string>
#include <random>
#include <condition_variable>

using namespace std;

void rest()
{
    std::random_device rd;
    std::mt19937 generator(rd());
    std::uniform_int_distribution<int> distribution(100, 5000);
    int dice_roll = distribution(generator);
    static std::chrono::milliseconds dura( dice_roll );
    this_thread::sleep_for(dura);
}

class Chopstick
{
public:
    Chopstick():_ready(true)
    {
    }

    void pickedUp()
    {
        unique_lock<mutex> lock(_lock); 
        while(!_ready) 
        { 
            _cond.wait(lock);
        }
        _ready = false;
    }

    void putDown()
    {
        lock_guard<mutex> lock(_lock);
        _ready = true;
        _cond.notify_one();
    }

private:
    mutex _lock;
    bool _ready;
    condition_variable _cond;
};

typedef shared_ptr<Chopstick> ChopstickPtr;

class Philosopher
{
public:
    Philosopher(string const& name, ChopstickPtr leftChopStick, ChopstickPtr rightChopStick):
        _name(name),
        _leftChopStick(leftChopStick), 
        _rightChopStick(rightChopStick),
        count()
    {
    }

    void run()
    {
        while (count++ < 1000)
        {
            eat();
        }
    }

private:
    void eat()
    {
        pickUpChopSticks();
        chew();
        putDownChopSticks();

        rest();
    }

    void pickUpChopSticks()
    {
        _leftChopStick->pickedUp();
        _rightChopStick->pickedUp();
    }

    void putDownChopSticks()
    {
        _rightChopStick->putDown();
        _leftChopStick->putDown();
    }

    void chew()
    {
        cout << _name.c_str() << " is chewing." << endl;
    }

private:
    ChopstickPtr _leftChopStick;
    ChopstickPtr _rightChopStick;
    string _name;
    int count;
};

void run(Philosopher& philosopher)
{
    philosopher.run();
}

void testPhilosopherEat()
{
    ChopstickPtr chopSticks[3] = {make_shared<Chopstick>(), 
        make_shared<Chopstick>(), 
        make_shared<Chopstick>()};
    Philosopher philosophers[3] = {Philosopher("p0", chopSticks[0], chopSticks[1]),
        Philosopher("p1", chopSticks[1], chopSticks[2]),
        Philosopher("p2", chopSticks[2], chopSticks[0])};

    thread first(run, philosophers[0]);
    thread second(run, philosophers[1]);
    thread third(run, philosophers[2]);

    first.detach();
    second.detach();
    third.detach();

    while (!first.joinable() && 
        !second.joinable() && 
        !third.joinable())
    {
        std::this_thread::sleep_for( std::chrono::milliseconds ( 2000 ) );
    }

}

int _tmain(int argc, _TCHAR* argv[])
{
    testPhilosopherEat();
	return 0;
}

